/////////////////////////////////////////////////////////////////////////////
// Original code from libhdfs3. Copyright (c) 2013 - 2014, Pivotal Inc.
// All rights reserved. Author: Zhanwei Wang
/////////////////////////////////////////////////////////////////////////////
//  Modifications by Kumo Inc.
// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//



#include <kmhdfs/common/exception.h>
#include <kmhdfs/common/exception_internal.h>
#include <kmhdfs/common/hash.h>
#include <kmhdfs/client/token.h>
#include <kmhdfs/common/writable_utils.h>

#include <gsasl.h>

using namespace Hdfs::Internal;

namespace Hdfs {
    namespace Internal {
        static std::string Base64Encode(const char *input, size_t len) {
            int rc = 0;
            size_t outLen;
            char *output = NULL;
            std::string retval;

            if (GSASL_OK != (rc = gsasl_base64_to(input, len, &output, &outLen))) {
                assert(GSASL_MALLOC_ERROR == rc);
                throw std::bad_alloc();
            }

            assert(NULL != output);
            retval = output;
            gsasl_free(output);

            for (size_t i = 0; i < retval.length(); ++i) {
                switch (retval[i]) {
                    case '+':
                        retval[i] = '-';
                        break;

                    case '/':
                        retval[i] = '_';
                        break;

                    case '=':
                        retval.resize(i);
                        break;

                    default:
                        break;
                }
            }

            return retval;
        }

        static void Base64Decode(const std::string &urlSafe,
                                 std::vector<char> &buffer) {
            int retval = 0, append = 0;
            size_t outLen;
            char *output = NULL;
            std::string input = urlSafe;

            for (size_t i = 0; i < input.length(); ++i) {
                switch (input[i]) {
                    case '-':
                        input[i] = '+';
                        break;

                    case '_':
                        input[i] = '/';
                        break;

                    default:
                        break;
                }
            }

            while (true) {
                retval = gsasl_base64_from(input.data(), input.length(), &output, &outLen);

                if (GSASL_OK != retval) {
                    switch (retval) {
                        case GSASL_BASE64_ERROR:
                            if (append++ < 2) {
                                input.append("=");
                                continue;
                            }

                            throw std::invalid_argument(
                                "invalid input of gsasl_base64_from");

                        case GSASL_MALLOC_ERROR:
                            throw std::bad_alloc();

                        default:
                            assert(
                                false
                                && "unexpected return value from gsasl_base64_from");
                    }
                }

                break;
            }

            assert(outLen >= 0);
            buffer.resize(outLen);
            memcpy(buffer.data(), output, outLen);
            gsasl_free(output);
        }

        std::string Token::toString() const {
            try {
                size_t len = 0;
                std::vector<char> buffer(1024);
                WritableUtils out(buffer.data(), buffer.size());
                len += out.WriteInt32(identifier.size());
                len += out.WriteRaw(identifier.data(), identifier.size());
                len += out.WriteInt32(password.size());
                len += out.WriteRaw(password.data(), password.size());
                len += out.WriteText(kind);
                len += out.WriteText(service);
                return Base64Encode(buffer.data(), len);
            } catch (...) {
                NESTED_THROW(HdfsIOException, "cannot convert token to string");
            }
        }

        Token &Token::fromString(const std::string &str) {
            int32_t len;

            try {
                std::vector<char> buffer;
                Base64Decode(str, buffer);
                WritableUtils in(buffer.data(), buffer.size());
                len = in.ReadInt32();
                identifier.resize(len);
                in.ReadRaw(identifier.data(), len);
                len = in.ReadInt32();
                password.resize(len);
                in.ReadRaw(password.data(), len);
                kind = in.ReadText();
                service = in.ReadText();
                return *this;
            } catch (...) {
                NESTED_THROW(HdfsInvalidBlockToken,
                             "cannot construct a token from the string");
            }
        }

        size_t Token::hash_value() const {
            size_t values[] = {
                StringHasher(identifier), StringHasher(password),
                StringHasher(kind), StringHasher(service)
            };
            return CombineHasher(values, sizeof(values) / sizeof(values[0]));
        }
    }
}
